package solver.moves;

import java.util.*;

import command.Command;
import command.IncrementalMoveProcessToMachineCommand;
import command.Invoker;
import command.MoveFactory;
import command.MoveProcessToMachineCommand;

import model.*;

public abstract class AbstractMove {
	
	SolverEnvironment env;
		
	MoveStatistics stat = new MoveStatistics();
		
	public AbstractMove(SolverEnvironment env){
		setEnv(env);
		init();
	}
	
	protected void init(){
		//Placeholder to create some initialization in a move during its creation 
	}
	
	public void setEnv(SolverEnvironment env){
		this.env = env;
		env.registerMove(this);
		init();
	}
	

	public List<Integer> getSelectCandidateProcesses(){
		return null;
	}
	
	public int getSingleCandiadateProcess(){
		return getRandom().nextInt(getProblem().P);
	}
	
	public List<Integer> getSelectCandidateMachines(){
		return null;
	}
	
	public abstract boolean performMove();
	
	public long getImprovement(){
		return 0;
	}
	
	public boolean testAndUndo(int p_index, int m_index) {
		int orig_m_index = getCurrent_solution().getMachineIdForProcessId(p_index);
		if(getIv().isProbablyFeasibleNoSchedule(p_index, orig_m_index, m_index))
			return makeMoveAndUndo(p_index,m_index);
		else
			return false;
	}
	
	public boolean testWithoutMove(int p_index, int m_index) {
		int orig_m_index = getCurrent_solution().getMachineIdForProcessId(p_index);
		return testWithoutMove(p_index, orig_m_index, m_index);
	}
	
	public boolean testWithoutMove(int p_index, int orig_m_index, int m_index) {
		return getIv().isProbablyFeasibleNoSchedule(p_index, orig_m_index, m_index) &&
				getIv().isFinallyFeasibleNoSchedule(p_index, orig_m_index, m_index);
	}
	
	public void makeMove(int p_index, int m_index) {
		int orig_m_index = getCurrent_solution().getMachineIdForProcessId(p_index);
		makeMove(p_index, orig_m_index,m_index);
	}
	
	public void makeMove(int p_index, int orig_m_index, int m_index) {
		IncrementalMoveProcessToMachineCommand cmd = new IncrementalMoveProcessToMachineCommand(getIv(), getCc());
//		IncrementalMoveProcessToMachineCommand cmd = MoveFactory.getMove();
		cmd.setP_id(p_index);
		cmd.setM_id(m_index);
		getInvoker().addCommand(cmd);
		getInvoker().executeLastCommand();
	}
	
	
	public boolean makeMoveAndUndo(int p_index, int m_index) {
		int orig_m_index = getCurrent_solution().getMachineIdForProcessId(p_index);
		
		//Calculate the single process move cost
		getCc().getCurrentProcessMoveToMachineCost(p_index, m_index);
		
//		cc.getRoughProcessMoveToMachineCost(current_solution, p_index,orig_m_index,m_index);
		
		IncrementalMoveProcessToMachineCommand cmd = new IncrementalMoveProcessToMachineCommand(getIv(), getCc());
//		IncrementalMoveProcessToMachineCommand cmd = MoveFactory.getMove();
		cmd.setP_id(p_index);
		cmd.setM_id(m_index);
		getInvoker().addCommand(cmd);
		getInvoker().executeLastCommand();
		
		if(!getIv().isFinallyFeasible(p_index, orig_m_index, m_index)){
			getInvoker().undoLastCommand();
			return false;
		}
		else{
//			ece.computeCost();
//			assert((ece.getNewSolutionCost()==potential_cost)) : ece.getNewSolutionCost()+"!="+potential_cost;
//			potential_cost=ece.getNewSolutionCost();
			return true;
		}
	}
	
	protected boolean testSequentially(int p_index, int start, int end){
		boolean mFound=false;
		for(int i=start; i<end;i++){
			int m_index = i%getProblem().M;
			if(testAndUndo(p_index, m_index)){
				mFound=true;
				break;
			}
		}
		return mFound;
	}
	
	protected int testSequentiallyNoSchedule(int p_index, int orig_m_index, int start, int end){
		int mFound=-1;
		for(int i=start; i<end;i++){
			int m_index = i%getProblem().M;
			if(testWithoutMove(p_index, orig_m_index, m_index)){
				mFound=m_index;
				break;
			}
		}
		return mFound;
	}
	
	protected boolean testSequentiallyBest(int p_index, int start, int end){
		boolean mFound=false;
		long init_cost=getCc().getNewSolutionCost(); 
		long bestCost=Long.MAX_VALUE; 
		int best_mIndex=-1;
		
		for(int i=start; i<end;i++){
			int m_index = i%getProblem().M;
			if(testAndUndo(p_index, m_index)){
				long new_cost = getCc().getPotentialSolutionCost();
				if(new_cost<=bestCost){
					mFound=true;
					bestCost = new_cost;
					best_mIndex = m_index;
				}
				getInvoker().undoLastCommand();
			}
		}
		
		assert(getCc().getNewSolutionCost()==init_cost);
		
		if(best_mIndex!=-1){
			if(testAndUndo(p_index, best_mIndex)){			//This is not really needed	
				return true;
			}
			else
				return false;
		}
		
		return mFound;
	}
	
	protected int testSequentiallyBestNoSchedule(int p_index, int orig_m, int start, int end){
		long bestCost=Long.MAX_VALUE; 
		int best_mIndex=-1;
		Solution pot_sol = env.getPotentialSolution();
		for(int i=start; i<end;i++){
			int m_index = i%getProblem().M;
			if(testWithoutMove(p_index, m_index)){
				long delta = getCc().getProcessMoveToMachineCost(pot_sol, p_index,orig_m, m_index);
				if(delta<=bestCost){
					bestCost = delta;
					best_mIndex = m_index;
				}
			}
		}			
		return best_mIndex;
	}
		
	protected boolean testRandom(int p_index, int num){
		boolean mFound=false;
		do{
			int m_index = getRandom().nextInt(getProblem().M);
			if(testAndUndo(p_index, m_index)){
				mFound=true;
				break;
			}
		}while(--num>0);
		return mFound;
	}
	
	protected int testRandomNoSchedule(int p_index, int orig_m_id, int num){
		int mFound=-1;
		do{
			int m_index = getRandom().nextInt(getProblem().M);
			if(testWithoutMove(p_index, orig_m_id, m_index)){
				mFound=m_index;
				break;
			}
		}while(--num>0);
		return mFound;
	}
	
	protected void makeExchange(int p_index1, int p_index2) {
		int m_index1 = env.getPotentialSolution().getMachineIdForProcessId(p_index1);
		int m_index2 = env.getPotentialSolution().getMachineIdForProcessId(p_index2);
		makeExchange(p_index1,m_index1, p_index2, m_index2);
	}
	
	protected void makeExchange(int p_index1, int m_index1, int p_index2, int m_index2) {
			IncrementalMoveProcessToMachineCommand cmd1 = new IncrementalMoveProcessToMachineCommand(getIv(),getCc());
//			IncrementalMoveProcessToMachineCommand cmd1 = MoveFactory.getMove();
			cmd1.setP_id(p_index1);
			cmd1.setM_id(m_index2);
			getInvoker().addCommand(cmd1);
			getInvoker().executeLastCommand();

			IncrementalMoveProcessToMachineCommand cmd2 = new IncrementalMoveProcessToMachineCommand(getIv(),getCc());
//			IncrementalMoveProcessToMachineCommand cmd2 = MoveFactory.getMove();
			cmd2.setP_id(p_index2);
			cmd2.setM_id(m_index1);
			getInvoker().addCommand(cmd2);
			getInvoker().executeLastCommand();
	}
	
	protected boolean testAndMakeExchangeNoSchedule(int p_index1, int p_index2) {
		int m_index1 = getCurrent_solution().getMachineIdForProcessId(p_index1);
		int m_index2 = getCurrent_solution().getMachineIdForProcessId(p_index2);
		if(getIv().isProcessExchangeFeasibleNoSchedule(p_index1, m_index1, p_index2, m_index2)){
			getCc().getProcessExcahngeProfit(getCurrent_solution(), p_index1, p_index2, m_index1, m_index2);

			IncrementalMoveProcessToMachineCommand cmd1 = new IncrementalMoveProcessToMachineCommand(getIv(),getCc());
//			IncrementalMoveProcessToMachineCommand cmd1 = MoveFactory.getMove();
			cmd1.setP_id(p_index1);
			cmd1.setM_id(m_index2);
			getInvoker().addCommand(cmd1);
			getInvoker().executeLastCommand();

			IncrementalMoveProcessToMachineCommand cmd2 = new IncrementalMoveProcessToMachineCommand(getIv(),getCc());
//			IncrementalMoveProcessToMachineCommand cmd2 = MoveFactory.getMove();
			cmd2.setP_id(p_index2);
			cmd2.setM_id(m_index1);
			getInvoker().addCommand(cmd2);
			getInvoker().executeLastCommand();
			
			return true;
		}
		else
			return false;
	}
	
	protected boolean testAndMakeExchange(int p_index1, int p_index2) {
		int m_index1 = getCurrent_solution().getMachineIdForProcessId(p_index2);
		int m_index2 = getCurrent_solution().getMachineIdForProcessId(p_index1);
//		assert(getIv().isProcessExchangeFeasible(p_index1,p_index2)==getIv().isProcessExchangeFeasibleNoSchedule(p_index1,p_index2));
		if(getIv().isProcessExchangeFeasibleNoSchedule(p_index1,p_index2)){
			getCc().getProcessExcahngeProfit(getCurrent_solution(), p_index1, p_index2);

			IncrementalMoveProcessToMachineCommand cmd1 = new IncrementalMoveProcessToMachineCommand(getIv(),getCc());
//			IncrementalMoveProcessToMachineCommand cmd1 = MoveFactory.getMove();
			cmd1.setP_id(p_index1);
			cmd1.setM_id(m_index1);
			getInvoker().addCommand(cmd1);
			getInvoker().executeLastCommand();

			IncrementalMoveProcessToMachineCommand cmd2 = new IncrementalMoveProcessToMachineCommand(getIv(),getCc());
//			IncrementalMoveProcessToMachineCommand cmd2 = MoveFactory.getMove();
			cmd2.setP_id(p_index2);
			cmd2.setM_id(m_index2);
			getInvoker().addCommand(cmd2);
			getInvoker().executeLastCommand();
			
			return true;
		}
		else
			return false;
	}
	
	public void commit(){
		getInvoker().clearCommands();
	}
	
	public void undo(){
		getInvoker().undoAllCommands();
		getCc().restoreCostComponentsState();
	}


	
	public boolean executeMove() {
//		stat.incrCalls();
//		long prevCost= getCc().getNewSolutionCost();
		getCc().saveCostComponentsState();
//		boolean res = performMove();
//		if (res){
//			stat.incrSuccesses();
//			long newCost = getCc().getPotentialSolutionCost();
//			if(newCost==prevCost)
//				stat.incrSamecost();
//			else if(newCost<prevCost)
//				stat.incrImproving();
//			else
//				stat.incrWorsening();
//		}
//		else
//			stat.incrFailed();
//		return res;
		
		return performMove();
	}

	@Override
	public String toString() {
		return getClass().getName()+getInvoker().getCommands().toString();
	}
	
	public String getStatistics() {
		return getClass().getName()+stat.toString();
	}

	Problem getProblem() {
		return env.getProblem();
	}

	Solution getInitial_solution() {
		return env.getInitialSolution();
	}

	Solution getCurrent_solution() {
		return env.getPotentialSolution();
	}

	Random getRandom() {
		return env.getRandom();
	}

	Invoker getInvoker() {
		return env.getInvoker();
	}

	IncrementalValidator getIv() {
		return env.getValidator();
	}

	IncrementalCostCalculator getCc() {
		return env.getCostCalculator();
	}

	public MoveStatistics getStat() {
		return stat;
	}
	
//	private ArrayList<Integer> populateKCwithPeriods(ArrayList<Integer> kempe_chain, Integer cur_vertex, Set<Integer> c1_list, Set<Integer> c2_list, int c1, int c2){
//		
//		for(int i=0;i<compressed_conflict_matrix[cur_vertex].length;i++){
//			int exam_id2=compressed_conflict_matrix[cur_vertex][i];
//			if(c2_list.contains(exam_id2)){
//				kempe_chain.add(exam_id2);
//				kempe_chain.add(c1);
//				c2_list.remove(exam_id2);
//				populateKCwithPeriods(kempe_chain, exam_id2, c2_list, c1_list,c2,c1);
//			}
//		}
//		return kempe_chain;
//	}
//	
//	public int[][] getKempeChainsArray(int m_id_1, int m_id_2) {
//		Set<Integer> c1_list = new HashSet<Integer>(current_solution.getProcessesForMachine(m_id_1));
//		Set<Integer> c2_list = new HashSet<Integer>(current_solution.getProcessesForMachine(m_id_2));
//		
//		ArrayList<int[]> akc = new ArrayList<int[]>();
//		ArrayList<Integer> kempe_chain = new ArrayList<Integer>();
//		//We create all the independent exam sets for c1 
//		while(!c1_list.isEmpty()){
//			kempe_chain.clear();
//			Iterator<Integer> iter = c1_list.iterator();
//			Integer cur_vertex = iter.next(); 
//			kempe_chain.add(cur_vertex);
//			c1_list.remove(cur_vertex);
//			kempe_chain.add(m_id_2);
//			kempe_chain = populateKCwithPeriods(kempe_chain, cur_vertex, c1_list, c2_list, m_id_1, m_id_2);
//			int[] kc_array = new int[kempe_chain.size()];
//			int i=0;
//			for (Integer kc: kempe_chain) {
//				kc_array[i++]=kc.intValue();			
//			}
//			akc.add(kc_array);
//		}
//		
//		//Only add simple moves for c2. The other are covered by c1 
//		for(Integer cur_vertex : c2_list){			
//			akc.add(new int[]{cur_vertex.intValue(),m_id_1});
//		}
//				
//		//Now akc has the single groups only. We should generate all possible combinations
////		ArrayList<int[]> combs = new ArrayList<List<Integer>>();
////		int kMax = akc.size();//(akc.size()<5)?akc.size():5; // At most akc.size(); original had kMax=1;
////		generateSimpleKCCombinations(combs,akc,kMax,10*akc.size());
////		akc = combs;
//////		System.out.println(akc);
//////		System.out.println(ret);
//		int[][] ret = new int[akc.size()][];
//		int i=0;
//		for (int[] kc: akc) {
//			ret[i++]=kc;			
//		}
//		return ret;
//	}
	
	protected boolean testSequentiallyForceBestNoSchedule(int p_index, int orig_m, int start, int end){
		long bestCost=getCc().getNewSolutionCost(); 
		int best_mIndex=orig_m;
		Solution pot_sol = env.getPotentialSolution();
		List<Command> replay_list = null;
		
		for(int i=start; i<end;i++){
			int m_index = i%getProblem().M;
			if(testWithoutMove(p_index, m_index)){
				long delta = getCc().getProcessMoveToMachineCost(pot_sol, p_index,orig_m, m_index);
				if(delta<=bestCost){
					bestCost = delta;
					best_mIndex = m_index;
				}
			}
			else{
				Invoker inv = new Invoker();
				if(forceSchedule(bestCost, p_index, m_index, inv)){
					long new_cost = env.getCostCalculator().getNewSolutionCost();
					if (new_cost < bestCost) {
						bestCost = new_cost;
						best_mIndex = m_index;
						replay_list = new ArrayList<Command>(inv.getCommands().size());
						for(Command c: inv.getCommands())
							replay_list.add(c);
					}
				}
				inv.undoAllCommands();
			}
		}
		// By now we should have the best position of the current process
		// The move could create other processes to move to other positions (replayList)

		if(best_mIndex!=orig_m){
			getCc().getProcessMoveToMachineCost(pot_sol, p_index,orig_m, best_mIndex);
			makeMove(p_index, orig_m, best_mIndex);
			getCc().updateNewSolutionCost();
			if(replay_list!=null){
				for(Command c: replay_list){// Found a better position with conflicts
					int p = ((IncrementalMoveProcessToMachineCommand)c).getP_id();
					int o_m = pot_sol.getMachineIdForProcessId(p);
					getCc().getProcessMoveToMachineCost(pot_sol,
							p,
							o_m,
							((IncrementalMoveProcessToMachineCommand)c).getM_id());
					getInvoker().addCommand(c);
					getInvoker().executeLastCommand();
					getCc().updateNewSolutionCost();
				}
			}
			return true;
		}
		return false;
	}

private boolean forceSchedule(long best_cost, int pId, int mId,	Invoker invoker) {
//		List<Integer> removeList = env.getPotentialSolution().getScheduledExamsInConflictWith(pId, mId);
//		for(Integer k:removeList){
//			invoker.addCommand(new UnscheduleExam(bb, k));
//			invoker.executeLastCommand();
//		}
//		invoker.addCommand(new ScheduleExam(bb, pId, mId));
//		invoker.executeLastCommand();
//
//		boolean canSchedule = true;
//
//		for(Integer k:removeList){
//
//			int local_start_period = rng.nextInt(problem.P);
//			int local_best_period = -1;
//			int local_best_cost = Integer.MAX_VALUE;
//
//			for (int local_period_id1_ = local_start_period; local_period_id1_ < problem.P
//			+ local_start_period; local_period_id1_++){
//				int local_period_id1 = local_period_id1_%problem.P;
//				if(bb.hasConflict(k.intValue(), local_period_id1))
//					continue;
//				invoker.addCommand(new ScheduleExam(bb, k.intValue(), local_period_id1));
//				invoker.executeLastCommand();
//				if (bb.getCachedCost() < best_cost) {
//					if(bb.getCachedCost() < local_best_cost){
//						local_best_cost = bb.getCachedCost();
//						local_best_period = local_period_id1;
//					}
//				}
//				invoker.undoLastCommand();
//			}
//
//			if (local_best_period!=-1){
//				invoker.addCommand(new ScheduleExam(bb, k.intValue(), local_best_period));
//				invoker.executeLastCommand();
//			}
//			else{
//				canSchedule = false;
//				break;
//			}
//		}//removeList
//
//		if(canSchedule==false){
//			invoker.undoAllCommands();
//			return false;
//		}
//		else
			return true;
	}
}
